<?php
/**
 * 数据库连接
 */
namespace library\Core\Db;
use library\Core\Exception\DbException;

class CDbConnection
{
    public $dbConfig = NULL;
    /**
     * @var string Custom PDO wrapper class.
     * @since 1.1.8
     */
    public $pdoClass = 'PDO';
    
    public $pdoDbType = 'mysql';

    
    private $host;
    private $port;
    private $username;
    private $password;
    private $dbname;
    private $autoConnect;
    private $charset;
   

    private $_attributes=array();
    private $_active=false;
    private $_pdo;
    private $_transaction;
    private $_schema;


    /**
     * Constructor.
     * Note, the DB connection is not established when this connection
     * instance is created. Set {@link setActive active} property to true
     * to establish the connection.
     * @param string $dsn The Data Source Name, or DSN, contains the information required to connect to the database.
     * @param string $username The user name for the DSN string.
     * @param string $password The password for the DSN string.
     * @see http://www.php.net/manual/en/function.PDO-construct.php
     */
    public function __construct($dbConfig)
    {
        $this->dbConfig = $dbConfig;
        $this->init();
    }

    /**
     * Close the connection when serializing.
     * @return array
     */
    public function __sleep()
    {
        $this->close();
        return array_keys(get_object_vars($this));
    }

    /**
     * Returns a list of available PDO drivers.
     * @return array list of available PDO drivers
     * @see http://www.php.net/manual/en/function.PDO-getAvailableDrivers.php
     */
    public static function getAvailableDrivers()
    {
        return \PDO::getAvailableDrivers();
    }

    /**
     * Initializes the component.
     * This method is required by {@link IApplicationComponent} and is invoked by application
     * when the CDbConnection is used as an application component.
     * If you override this method, make sure to call the parent implementation
     * so that the component can be marked as initialized.
     */
    public function init()
    {
        if(empty($this->dbConfig))
            throw new DbException('CDbConnection.dbConfig cannot be empty.');
        
        $this->host = isset($this->dbConfig['host']) ? $this->dbConfig['host'] : '';
        $this->prot = isset($this->dbConfig['prot']) ? $this->dbConfig['prot'] : '3306';
        $this->username = isset($this->dbConfig['username']) ? $this->dbConfig['username'] : '';
        $this->password = isset($this->dbConfig['password']) ? $this->dbConfig['password'] : '';
        $this->dbname = isset($this->dbConfig['dbname']) ? $this->dbConfig['dbname'] : '';
        $this->autoConnect = isset($this->dbConfig['autoConnect']) ? $this->dbConfig['autoConnect'] : true;
        $this->charset = isset($this->dbConfig['charset']) ? $this->dbConfig['charset'] : '';
        
        //parent::init();
        if($this->autoConnect)
            $this->setActive(true);
    }

    /**
     * Open or close the DB connection.
     * @param boolean $value whether to open or close DB connection
     * @throws CException if connection fails
     */
    public function setActive($value)
    {
        if($value!=$this->_active)
        {
            if($value)
                $this->open();
            else
                $this->close();
        }
    }

    /**
     * Opens DB connection if it is currently not
     * @throws CException if connection fails
     */
    protected function open()
    {
        if($this->_pdo===null)
        {
            
            try
            {
                //Yii::trace('Opening DB connection','system.db.CDbConnection');
                $this->_pdo=$this->createPdoInstance();
                $this->initConnection();
                
                $this->_active=true;
                
            }catch(PDOException $e){
                throw new DbException('CDbConnection failed to open the DB connection!', $e->getCode() ,$e->errorInfo);
            }
        }
    }

    /**
     * Closes the currently active DB connection.
     * It does nothing if the connection is already closed.
     */
    protected function close()
    {
        //Yii::trace('Closing DB connection','system.db.CDbConnection');
        $this->_pdo=null;
        $this->_active=false;
        $this->_schema=null;
    }

    /**
     * Creates the PDO instance.
     * When some functionalities are missing in the pdo driver, we may use
     * an adapter class to provide them.
     * @throws CDbException when failed to open DB connection
     * @return PDO the pdo instance
     */
    protected function createPdoInstance()
    {
        $pdoClass=$this->pdoClass;
        
        if(!class_exists($pdoClass))
            throw new DbException('CDbConnection is unable to find PDO class "'.$pdoClass.'". Make sure PDO is installed correctly.');

        $connectionString = $this->pdoDbType.':host=' .$this->host. 
                            ';port=' .$this->port. 
                            ';dbname=' .$this->dbname.';';
        if(!empty($this->dbConfig['charset'])){
            $connectionString .= 'charset='.$this->dbConfig['charset'].';';
        }
        
        @$instance=new $pdoClass($connectionString,$this->username,$this->password,$this->_attributes);

        if(!$instance)
            throw new DbException('CDbConnection failed to open the DB connection.');

        return $instance;
    }

    /**
     * Initializes the open db connection.
     * This method is invoked right after the db connection is established.
     * The default implementation is to set the charset for MySQL and PostgreSQL database connections.
     * @param PDO $pdo the PDO instance
     */
    protected function initConnection()
    {
        $this->_pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
        
//         if($this->dbConfig['charset']!==null)
//         {
//             $driver=strtolower($this->_pdo->getAttribute(\PDO::ATTR_DRIVER_NAME));
//             if(in_array($driver,array('pgsql','mysql','mysqli')))
//                 $this->_pdo->exec('SET NAMES '.$this->_pdo->quote($this->dbConfig['charset']));
//         }
        
        return true;
    }

    
    /**
     * Returns the PDO instance.
     * @return PDO the PDO instance, null if the connection is not established yet
     */
    public function getPdoInstance()
    {
        return $this->_pdo;
    }

    /**
     * Returns the currently active transaction.
     * @return CDbTransaction the currently active transaction. Null if no active transaction.
     */
    public function getCurrentTransaction()
    {
        if($this->_transaction!==null)
        {
            if($this->_transaction->getActive())
                return $this->_transaction;
        }
        return null;
    }

    /**
     * Starts a transaction.
     * @return CDbTransaction the transaction initiated
     */
    public function beginTransaction()
    {
        $this->setActive(true);
        $this->_transaction = $this->_pdo->beginTransaction();
        return $this->_transaction;
    }

    public function commit()
    {
        if($this->_transaction)
        {
            $this->_pdo->commit();
            //$this->_active=false;
            $this->_transaction = false;
        }
        else
            throw new DbException('CDbTransaction is inactive and cannot perform commit or roll back operations.');
    }
    
    /**
     * Rolls back a transaction.
     * @throws CException if the transaction or the DB connection is not active.
     */
    public function rollback()
    {
        if($this->_transaction)
        {
            $this->_pdo->rollBack();
            //$this->_active=false;
            $this->_transaction = false;
        }
        else
            throw new DbException('CDbTransaction is inactive and cannot perform commit or roll back operations.');
    }
    
    
    
    /**
     * Returns the ID of the last inserted row or sequence value.
     * @param string $sequenceName name of the sequence object (required by some DBMS)
     * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
     * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
     */
    public function getLastInsertID($sequenceName='')
    {
        $this->setActive(true);
        return $this->_pdo->lastInsertId($sequenceName);
    }

    /**
     * Quotes a string value for use in a query.
     * @param string $str string to be quoted
     * @return string the properly quoted string
     * @see http://www.php.net/manual/en/function.PDO-quote.php
     */
    public function quoteValue($str)
    {
        if(is_int($str) || is_float($str))
            return $str;

        $this->setActive(true);
        if(($value=$this->_pdo->quote($str))!==false)
            return $value;
        else  // the driver doesn't support quote (e.g. oci)
            return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
    }

    /**
     * Determines the PDO type for the specified PHP type.
     * @param string $type The PHP type (obtained by gettype() call).
     * @return integer the corresponding PDO type
     */
    public function getPdoType($type)
    {
        static $map=array
        (
            'boolean'=>\PDO::PARAM_BOOL,
            'integer'=>\PDO::PARAM_INT,
            'string'=>\PDO::PARAM_STR,
            'resource'=>\PDO::PARAM_LOB,
            'NULL'=>\PDO::PARAM_NULL,
        );
        return isset($map[$type]) ? $map[$type] : \PDO::PARAM_STR;
    }

    /**
     * Returns the case of the column names
     * @return mixed the case of the column names
     * @see http://www.php.net/manual/en/pdo.setattribute.php
     */
    public function getColumnCase()
    {
        return $this->getAttribute(\PDO::ATTR_CASE);
    }

    /**
     * Sets the case of the column names.
     * @param mixed $value the case of the column names
     * @see http://www.php.net/manual/en/pdo.setattribute.php
     */
    public function setColumnCase($value)
    {
        $this->setAttribute(\PDO::ATTR_CASE,$value);
    }

    public function setFetchTableName($value){
        $this->setAttribute(\PDO::ATTR_FETCH_TABLE_NAMES,$value);
    }

    /**
     * Returns how the null and empty strings are converted.
     * @return mixed how the null and empty strings are converted
     * @see http://www.php.net/manual/en/pdo.setattribute.php
     */
    public function getNullConversion()
    {
        return $this->getAttribute(\PDO::ATTR_ORACLE_NULLS);
    }

    /**
     * Sets how the null and empty strings are converted.
     * @param mixed $value how the null and empty strings are converted
     * @see http://www.php.net/manual/en/pdo.setattribute.php
     */
    public function setNullConversion($value)
    {
        $this->setAttribute(\PDO::ATTR_ORACLE_NULLS,$value);
    }

    /**
     * Returns whether creating or updating a DB record will be automatically committed.
     * Some DBMS (such as sqlite) may not support this feature.
     * @return boolean whether creating or updating a DB record will be automatically committed.
     */
    public function getAutoCommit()
    {
        return $this->getAttribute(\PDO::ATTR_AUTOCOMMIT);
    }

    /**
     * Sets whether creating or updating a DB record will be automatically committed.
     * Some DBMS (such as sqlite) may not support this feature.
     * @param boolean $value whether creating or updating a DB record will be automatically committed.
     */
    public function setAutoCommit($value)
    {
        $this->setAttribute(\PDO::ATTR_AUTOCOMMIT,$value);
    }

    /**
     * Returns whether the connection is persistent or not.
     * Some DBMS (such as sqlite) may not support this feature.
     * @return boolean whether the connection is persistent or not
     */
    public function getPersistent()
    {
        return $this->getAttribute(\PDO::ATTR_PERSISTENT);
    }

    /**
     * Sets whether the connection is persistent or not.
     * Some DBMS (such as sqlite) may not support this feature.
     * @param boolean $value whether the connection is persistent or not
     */
    public function setPersistent($value)
    {
        return $this->setAttribute(\PDO::ATTR_PERSISTENT,$value);
    }

    /**
     * Returns the name of the DB driver
     * @return string name of the DB driver
     */
    public function getDriverName()
    {
        if(($pos=strpos($this->connectionString, ':'))!==false)
            return strtolower(substr($this->connectionString, 0, $pos));
        // return $this->getAttribute(\PDO::ATTR_DRIVER_NAME);
    }

    /**
     * Returns the version information of the DB driver.
     * @return string the version information of the DB driver
     */
    public function getClientVersion()
    {
        return $this->getAttribute(\PDO::ATTR_CLIENT_VERSION);
    }

    /**
     * Returns the status of the connection.
     * Some DBMS (such as sqlite) may not support this feature.
     * @return string the status of the connection
     */
    public function getConnectionStatus()
    {
        return $this->getAttribute(\PDO::ATTR_CONNECTION_STATUS);
    }

    /**
     * Returns whether the connection performs data prefetching.
     * @return boolean whether the connection performs data prefetching
     */
    public function getPrefetch()
    {
        return $this->getAttribute(\PDO::ATTR_PREFETCH);
    }

    /**
     * Returns the information of DBMS server.
     * @return string the information of DBMS server
     */
    public function getServerInfo()
    {
        return $this->getAttribute(\PDO::ATTR_SERVER_INFO);
    }

    /**
     * Returns the version information of DBMS server.
     * @return string the version information of DBMS server
     */
    public function getServerVersion()
    {
        return $this->getAttribute(\PDO::ATTR_SERVER_VERSION);
    }

    /**
     * Returns the timeout settings for the connection.
     * @return integer timeout settings for the connection
     */
    public function getTimeout()
    {
        return $this->getAttribute(\PDO::ATTR_TIMEOUT);
    }

    /**
     * Obtains a specific DB connection attribute information.
     * @param integer $name the attribute to be queried
     * @return mixed the corresponding attribute information
     * @see http://www.php.net/manual/en/function.PDO-getAttribute.php
     */
    public function getAttribute($name)
    {
        $this->setActive(true);
        return $this->_pdo->getAttribute($name);
    }

    /**
     * Sets an attribute on the database connection.
     * @param integer $name the attribute to be set
     * @param mixed $value the attribute value
     * @see http://www.php.net/manual/en/function.PDO-setAttribute.php
     */
    public function setAttribute($name,$value)
    {
        if($this->_pdo instanceof PDO)
            $this->_pdo->setAttribute($name,$value);
        else
            $this->_attributes[$name]=$value;
    }

    /**
     * Returns the attributes that are previously explicitly set for the DB connection.
     * @return array attributes (name=>value) that are previously explicitly set for the DB connection.
     * @see setAttributes
     * @since 1.1.7
     */
    public function getAttributes()
    {
        return $this->_attributes;
    }

    /**
     * Sets a set of attributes on the database connection.
     * @param array $values attributes (name=>value) to be set.
     * @see setAttribute
     * @since 1.1.7
     */
    public function setAttributes($values)
    {
        foreach($values as $name=>$value)
            $this->_attributes[$name]=$value;
    }
    
}
